/**
 * Copyright (C) 2011 the author
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.linguamathematica.oa4j;

import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import static java.lang.String.format;

import static java.net.URLEncoder.encode;
import static com.linguamathematica.oa4j.Base.ensure;
import static com.linguamathematica.oa4j.Base.*;

import com.linguamathematica.oa4j.schema.AmplifyResponse;

/**
 * The Class DefaultAnalysisService.
 */
public class DefaultAnalysisService implements AnalysisService
{
	/**
	 * The maximum number of characters supported by {@link DefaultAnalysisService#analyse(java.lang.String)}.
	 */
	public static final int MAX_TEXT_LENGTH = 5000;

	private final String key;
	private final HTTPService service;
	private final Unmarshaller unmarshaller;
	private final AnalysisFactory factory;

	/**
	 * Instantiates a new default analysis service.
	 * 
	 * @param key
	 *            the OpenAmplify key
	 * 
	 * @throws NullPointerException
	 *             if the key is null
	 */
	public DefaultAnalysisService(final String key)
	{
		this(key, new HTTPService(defaultOAWebServiceEndPoint()));
	}

	/**
	 * Instantiates a new default analysis service.
	 * 
	 * @param key
	 *            the key
	 * @param service
	 *            the http service
	 * 
	 * @throws NullPointerException
	 *             if any of the arguments is null
	 */
	DefaultAnalysisService(final String key, final HTTPService service)
	{
		this(key, service, new AnalysisFactory());
	}

	/**
	 * Instantiates a new default analysis service.
	 * 
	 * @param key
	 *            the key
	 * @param service
	 *            the http service
	 * @param factory
	 *            the analysis factory
	 * 
	 * @throws NullPointerException
	 *             if any of the arguments is null
	 */
	DefaultAnalysisService(final String key, final HTTPService service, final AnalysisFactory factory)
	{
		this.key = key;
		this.service = service;
		this.factory = factory;
		unmarshaller = makeUnmarshaller();
	}

	/**
	 * Analyses the given text.
	 * 
	 * @param text
	 *            the text
	 * 
	 * @throws NullPointerException
	 *             if the text is null
	 * @throws IllegalArgumentException
	 *             if the text is empty, or greater than {@link DefaultAnalysisService#MAX_TEXT_LENGTH}
	 * @throws IllegalStateException
	 *             if the service has been shut down
	 */
	@Override
	public Analysis analyse(final String text)
	{
		ensureNotNull(text, "text is null");
		ensure(!text.isEmpty(), "text is empty");
		ensure(text.length() <= MAX_TEXT_LENGTH, "text is too large, was %s but should  have been %s at most",
				text.length(), MAX_TEXT_LENGTH);

		ensureState(!service.isShutDown(), "service has been shut down");

		return analysisFromResponseTo(service.query(builtFrom(text)));
	}

	/**
	 * Analyses the text in the given URL
	 * 
	 * @param url
	 *            the URL
	 * 
	 * @throws NullPointerException
	 *             if the URL is null
	 * 
	 * @throws IllegalStateException
	 *             if the service has been shut down
	 */
	@Override
	public Analysis analyse(final URL url)
	{
		ensureNotNull(url, "source URL is null");

		ensureState(!service.isShutDown(), "service has been shut down");

		return analysisFromResponseTo(service.query(builtFrom(url)));
	}

	private Analysis analysisFromResponseTo(final String response)
	{
		return factory.analysisFrom(unmarshall(response).getAmplifyReturn());
	}

	private String builtFrom(final String text)
	{
		try
		{
			return format("apiKey=%s&analysis=all&inputText=%s&outputFormat=XML", key, encode(text, "UTF-8"));
		}
		catch (final UnsupportedEncodingException exception)
		{
			throw new RequestException(format("Error while creating request for text %s. Report as defect.", text),
					exception);
		}
	}

	private String builtFrom(final URL url)
	{
		try
		{
			return format("apiKey=%s&analysis=all&sourceURL=%s&outputFormat=XML", key, encode(url.toString(), "UTF-8"));
		}
		catch (final UnsupportedEncodingException exception)
		{
			throw new RequestException(format("Error while creating request for url %s. Report as defect.", url),
					exception);
		}
	}

	private AmplifyResponse unmarshall(final String response)
	{
		try
		{
			return (AmplifyResponse) unmarshaller.unmarshal(new StringReader(response));
		}
		catch (final Exception exception)
		{
			throw new ResponseException(format("Error while reading response %s", response), 200, "OK", exception);
		}
	}

	private static URL defaultOAWebServiceEndPoint()
	{
		try
		{
			return new URL("http", "portaltnx20.openamplify.com", "/AmplifyWeb_v21/AmplifyThis");
		}
		catch (final MalformedURLException exception)
		{
			throw new RuntimeException(
					"Programming error whilst creating endpoint url [http://portaltnx20.openamplify.com/AmplifyWeb_v21/AmplifyThis]. Report as bug",
					exception);
		}
	}

	private static Unmarshaller makeUnmarshaller()
	{
		try
		{
			return JAXBContext.newInstance(AmplifyResponse.class.getPackage().getName()).createUnmarshaller();
		}
		catch (final JAXBException exception)
		{
			throw new IllegalStateException("Error during creation of xml unmarshaller for package "
					+ AmplifyResponse.class.getPackage().getName(), exception);
		}
	}

	/**
	 * Shuts down the service, releasing any resources
	 * 
	 */
	@Override
	public void shutDown()
	{
		service.shutDown();
	}
}
